import logging
import os
import re
import socket
import string
import sys

import util.json as json
import io.ioloop

import atom
import config
import db_backend
import guid
import transport
import verset
import version

log = logging.getLogger('sync')

# -----------------------------------------------------------------------------
class Sync:
    """Disconnected object syncing system."""

    def __init__(self, config_inst = None, db = None, 
	         atom_store = None, transport_inst = None):
	if config_inst:
	    self.config_ = config_inst
	else:
	    self.config_ = config.SyncConfig()
	    self.config_.parse_opts()

	self.db_ = db or self.init_db()

        self.config_.sync_opts_with_db(self.db_)
        
        self.verset_factory_ = verset.VerSetFactory(self.db_)
	self.verset_factory_.init_db(self.config_.init_db_)

	self.atom_store_ = (atom_store or 
	                    atom.AtomStore(self, self.config_.init_db_,
			                   self.config_.type_file_))

        if self.config_.quit_after_init_:
            log.info('quit_after_init_ is set, quitting...')
            sys.exit(0)
        
	self.transport_ = (transport_inst or 
	                   transport.Transport(self, self.config_.sched_module_,
                                               self.config_.tx_ap_))
	self.transport_.connect()

    def init_db(self):
        log.info('---- Initializing database')
        config = self.config_
        if config.db_type_ == 'mysql':
            return db_backend.MySQL(config.db_opts_['HOST'],
				    config.db_opts_['USER'],
				    config.db_opts_['PASSWD'],
				    config.db_opts_['DBNAME'],
				    config.init_db_)
        elif config.db_type_ == 'sqlite':
            return db_backend.Sqlite(config.db_opts_['FILE'],
				     config.init_db_)
        else:
            raise Exception('Invalid DB type: ' + config.db_type_)

    def atom_store(self):
        return self.atom_store_

    def own_id(self):
        return self.config_.id_
        
    def get_peer_vs(self, peer_id):
        return self.verset_factory_.peer_set(peer_id)

    def get_own_vs(self):
        return self.get_peer_vs(self.config_.id_)
        
    def run(self):
        io.ioloop.master_loop.set_alarm(
            io.ioloop.Alarm(self.config_.sync_interval_, self.sync_alarm))
        io.ioloop.master_loop.run()

    def sync_alarm(self):
        """Method that runs the sync process."""
        self.sync_local()
        io.ioloop.master_loop.set_alarm(
            io.ioloop.Alarm(self.config_.sync_interval_, self.sync_alarm))
        
    def sync_local(self):
	"""Scan the Neon meta table for any new guids that we haven't
	processed for transport."""
        log.debug('scanning for local updates')
	for g in self.atom_store_.get_new_local_atoms():
            log.debug('sync_local: %s' % g)
	    self.get_own_vs().set_ver(g.id(), g.ver(), 1)
            # XXX/bowei -- this needs to look at the scheduler
	    self.transport_.update_scheduler((g.id(), g.ver()))
	    self.atom_store_.set_local_seen(g)
            self.db_.commit()
        # XXX/bowei -- this needs to look at the scheduler
        self.transport_.check_reqs()

    def get_atom(self, g):
        log.debug('get_atom(%s)' % g)
        ret = self.atom_store_.get_atom(g)
        return ret
    
    def put_atom(self, a):
        """Put called by the transport stack when a new Atom update has
        been received."""
        assert a.is_valid()
        log.debug('put_atom(%s)' % a)
        ret = self.atom_store_.put_atom(a)
        g = a.guid
        self.get_own_vs().set_ver(g.id(), g.ver(), 1)
        self.db_.commit()
        self.transport_.update_scheduler(g.as_tuple())
        self.transport_.check_reqs()
        return ret

    def del_atom(self, guid):
	"""Delete an atom with the specified guid"""
        log.debug('del_atom(%s)' % guid)
        a = self.get_atom(guid)
        if a is None:
            return
        return self.atom_store_.del_atom(a)

# -----------------------------------------------------------------------------
#
# Main
#
if __name__ == '__main__':
    sync = Sync()
    sync.run()
